#include <Kharlia/Core/Common.h>
#include <Kharlia/Core/Tasker.h>
#include <Kharlia/Core/Subsystem.h>
#include <Kharlia/Core/Logger.h>
#include <Kharlia/Core/IO.h>
#include <Kharlia/Core/ObjectUtil.h>

#define DEFAULT_TASKLET_TIMEOUT 1000
#define SLEEP_YIELD_DISPATCH_ENABLED 1

// TODO: Make CTasker instances link to each other so that CApp can iterate
//       through them and check for tasklets.

namespace kharlia { namespace core {

static object GTaskletExtClass;

TPtr<CTasker> CTasker::msDefault;

namespace {
    
/// Check if the scheduler is empty.
bool IsSchedulerEmpty() {
    PyThreadState* ts = PyThreadState_GET();
    if (!ts || ts->st.main->next == ts->st.main)
        return true;
    return false;
}

} // namespace

CTasker::CTasker(
    PyObject* self,
    uint8 groupCount,
    FTaskletGroupOptions const& defaultOptions
):
    CSubsystem(self),
    mGroupCount(groupCount),
    mDefaultGroup(0),
    mCurrentGroup(NO_GROUPS),
    mLocalTime(0.0),
    mGroupOptions(groupCount),
    mGroups(groupCount)
{
    if (groupCount == 0 || groupCount > MAX_GROUP_COUNT)
        throw RuntimeError("invalid group count");
    for (uint i = 0; i < mGroups.size(); i++) {
        handle<PyChannelObject> channel(PyChannel_New(nullptr));
        PyChannel_SetPreference(channel.get(), 0);
        mGroups[i].YieldChannel = channel;
        mGroups[i].LastRunTime = 0.0;
    }
    for (uint i = 0; i < mGroupOptions.size(); i++) {
        mGroupOptions[i] = defaultOptions;
    }
}

CTasker::~CTasker() {
}

void CTasker::SetDefaultGroup(uint8 num) {
    if (num >= mGroupCount)
        throw ValueError("group number out of range");
    mDefaultGroup = num;
}

void CTasker::Tick(float deltaTime, uint8 group) {
    mLocalTime += deltaTime;
    if (group == ALL_GROUPS) {
        for (uint8 i = 0; i < mGroupCount; i++) {
            Run(i);
        }
    }
    else if (group != NO_GROUPS) {
        Run(group);
    }
}

void CTasker::Run(uint8 groupnum) {
    if (groupnum >= mGroupCount)
        throw RuntimeError("group number out of range");
    assert(mGroupCount == mGroups.size() && mGroupCount == mGroupOptions.size());

    FTaskletGroup& group = mGroups[groupnum];
    FTaskletGroupOptions& options = mGroupOptions[groupnum];
    bool isMain = IsCurrentTaskletMain();

    if (isMain && options.InsertStraysIntoDefault && !IsSchedulerEmpty()) {
        if (msDefault)
            StaticInsertRunnables();
        // If no default tasker is specified, we'll fail
        // and we'll just have to run the tasklets here
    }

    // Make pending inserts
    if (group.Inserts.size() > 0) {
        std::list<handle<PyTaskletObject>>::iterator it;
        for (it = group.Inserts.begin(); it != group.Inserts.end(); it++) {
            PyTaskletObject* tasklet = it->get();
            // If the tasklet isn't still paused, it is either dead
            // or scheduled/blocked elsewhere
            if (PyTasklet_Paused(tasklet)) {
                int iresult = PyTasklet_Insert(tasklet);
                if (iresult == -1)
                    throw_error_already_set();
            }

            it = group.Inserts.erase(it);
            if (it == group.Inserts.end())
                break;
        }
    }
    // Wake yielded tasklets
    // inline ClearYieldedChannel
    PyChannelObject* channel = group.YieldChannel.get();
    if (channel->balance) {
        handle<> elapsed(PyFloat_FromDouble(mLocalTime - group.LastRunTime));
        for (int i = channel->balance * -1; i > 0; i--) {
            if (PyChannel_Send(channel, elapsed.get()) == -1) {
                throw_error_already_set();
            }
        }
    }
    // Wake Sleeping Tasklets
    if (group.Sleepers.size() > 0) {
        std::list<FSleepingTasklet>::iterator it;
        for (it = group.Sleepers.begin(); it != group.Sleepers.end(); it++) {
            if (it->EndTime <= mLocalTime) {
                PyChannelObject* channel = it->Channel.get();
                int iresult = 0;
                if (channel->balance != 0) {
                    // Tasklets that hard switched won't need the delta
                    // send to them over the channel, so lets save a float
                    // object allocation.
                    if (it->SendDelta) {
                        handle<> elapsed(PyFloat_FromDouble(
                            mLocalTime - it->BeginTime
                            ));
                        iresult = PyChannel_Send(channel, elapsed.get());

                    }
                    else {
                        iresult = PyChannel_Send(channel, Py_None);
                    }
                }
                it = group.Sleepers.erase(it);
                if (iresult == -1)
                    throw_error_already_set();
                if (it == group.Sleepers.end())
                    break;
            }
        }
    }
    // Run tasklets
    if (isMain) {
        int flags = 0;
        if (options.ThreadBlock)
            flags |= Py_WATCHDOG_THREADBLOCK;
        if (options.SoftSwitch)
            flags |= PY_WATCHDOG_SOFT;
        if (options.IgnoreNesting)
            flags |= PY_WATCHDOG_IGNORE_NESTING;
        if (options.TotalTimeout)
            flags |= PY_WATCHDOG_TOTALTIMEOUT;
    
        mCurrentGroup = groupnum;
        PyObject* resultp = PyStackless_RunWatchdogEx(options.Timeout, flags);
        mCurrentGroup = NO_GROUPS;

        object result(handle<>(allow_null(resultp)));

        if (result.ptr() == nullptr) {
            GetMainLogger()->Critical("Unhandled exception from tasklet runner");
            if (!PyErr_Occurred())
                PyErr_SetString(PyExc_RuntimeError, "Unknown exception from tasklet runner");
            throw_error_already_set();
        }
        else if (!result.is_none()) {
            GetMainLogger()->Warning("exited stackless run on timeout");
            GetMainLogger()->Warning(str("tasklet: ") + str(result));
            // result should be the tasklet that was interrupted and removed form
            // the scheduler
            // TODO: Dispose of tasklets with individual tick counts that are too high
            assert(PyTasklet_Check(resultp));
            InsertTasklet(result, groupnum);
            InsertRunnables(groupnum);
        }
    }
    // Set if we should schedule ourselves
    else {
        // If we prefer the receivers, schedule ourself and let them run.
        // Otherwise, do nothing.
        if (options.RunPreference == -1) {
            handle<> result(PyStackless_Schedule(Py_None, 0));
        }
    }
    group.LastRunTime = mLocalTime;
}

object CTasker::CreateTasklet(object callable) {
    if (!callable.is_none() && PyString_Check(callable.ptr())) {
        callable = FindGlobal(callable);
    }
    return GTaskletExtClass(callable);
}

object CTasker::StaticCreateTasklet(object callable) {
    if (!msDefault)
        throw RuntimeError("no default tasker instance");
    return msDefault->CreateTasklet(callable);
}

bool CTasker::InsertTasklet(object taskletO, uint8 group, object args, object kwds) {
    if (!PyTasklet_Check(taskletO.ptr()))
        throw RuntimeError("not a tasklet");
    PyTaskletObject* tasklet = (PyTaskletObject*)taskletO.ptr();

    // If the tasklet has not yet been setup, do that now
    if (!PyTasklet_Alive(tasklet)) {
        if (args.is_none())  
            throw RuntimeError("attempted to insert dead tasklet without providing setup args");
        if (PyTasklet_Setup(tasklet, args.ptr(), kwds.ptr()) == -1)
            throw_error_already_set();
    }

    if (group == DEFAULT_GROUP)
        group = mDefaultGroup;

    // If the tasklet is already scheduled, check if the destination group is
    // currently running, otherwise unschedule the tasklet.
    if (PyTasklet_Scheduled(tasklet)) {
        if (group == mCurrentGroup)
            return true; // It is scheduled and should be run soon
        if (group == NEXT_GROUP && mCurrentGroup != NO_GROUPS)
            return true; // We can run it immediately
        if (PyTasklet_Remove(tasklet) == -1)
            throw_error_already_set();
    }

    // NEXT_GROUP becomes the default group if we're not scheduled or there
    // isn't already a group running.
    if (group == NEXT_GROUP)
        group = mDefaultGroup;
    if (group >= mGroupCount)
        throw ValueError("invalid group number");
    mGroups[group].Inserts.push_back(handle<PyTaskletObject>(borrowed(tasklet)));
    return true;
}

void CTasker::SetDefault(TPtr<CTasker> tasker) {
    if (!tasker)
        GetMainLogger()->Debug("set default tasker to None");
    msDefault = tasker;
}

bool CTasker::StaticInsertTasklet(object tasklet, object args, object kwds) {
    if (msDefault) {
        msDefault->InsertTasklet(tasklet, DEFAULT_GROUP, args, kwds);
        return true;
    }
    return false;
}

int CTasker::InsertRunnables(uint8 group) {
    using namespace py;

    int inserted = 0;
    PyTaskletObject *maint = nullptr, *t = nullptr;
    try {
        PyThreadState* ts = PyThreadState_GET();
        maint = (PyTaskletObject*)ts->st.main;
        Py_INCREF(maint);
        t = maint->next;
        while (t && PyTasklet_Check(t) && t != maint) {
            if (InsertTasklet(object(handle<>(borrowed(t))), group))
                inserted++;
            t = maint->next;
        }
        assert(IsSchedulerEmpty());
        Py_DECREF(maint);
    }
    catch (...) {
        Py_XDECREF(maint);
    }
    return inserted;
}

int CTasker::StaticInsertRunnables() {
    if (!msDefault)
        return 0;
    return msDefault->InsertRunnables(DEFAULT_GROUP);
}

bool CTasker::GetRealGroup(uint8& group) const {
    if (group == UNSPECIFIED_GROUP) {
        if (mCurrentGroup != NO_GROUPS)
            group = mCurrentGroup;
        else
            group = mDefaultGroup;
    }
    else if (group == CURRENT_GROUP) {
        if (mCurrentGroup == NO_GROUPS)
            return false;
        group = mCurrentGroup;
    }
    else if (group == DEFAULT_GROUP)
        group = mDefaultGroup;

    if (group >= mGroupCount)
        return false;

    return true;
}

handle<> CTasker::SleepImpl(float seconds, uint8 group, int sl) {
    if (!GetRealGroup(group))
        throw ValueError("invalid group number");

    handle<PyChannelObject> c(PyChannel_New(nullptr));
    PyChannel_SetPreference(c.get(), 0);

    float callTime = static_cast<float>(mLocalTime);

    mGroups[group].Sleepers.push_back(FSleepingTasklet(c, callTime, callTime + seconds, sl));

#if SLEEP_YIELD_DISPATCH_ENABLED
    CIOManager::AddPendingDispatchCall();
#endif

    return handle<>(PyChannel_Receive(c.get()));
}

float CTasker::Sleep(float seconds, uint8 group) {
    // Calling sleep from here requires
    // that we use hard switching
    assert(!slp_try_stackless);

    float calltime = static_cast<float>(mLocalTime);

    handle<> result = SleepImpl(seconds, group, false);
    assert(result.get() == Py_None);

    return static_cast<float>(mLocalTime) - calltime;
}

float CTasker::StaticSleep(float seconds, uint8 group) {
    if (!msDefault)
        throw RuntimeError("default tasker not set");
    return msDefault->Sleep(seconds, group);
}

// Macros to keep C++ exceptions from escaping into Python code.
#define BEGIN_CPP try {
#define END_CPP  } \
    catch (error_already_set&) {} \
    catch (PythonException& ex) { PyErr_SetString(ex.GetPyExcType(), ex.what()); } \
    catch (std::bad_alloc&) { PyErr_SetNone(PyExc_MemoryError); } \
    catch (std::exception& ex) { PyErr_SetString(PyExc_RuntimeError, ex.what()); } \
    catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception"); }

PyObject* CTasker::SleepPy(PyObject* selfb, PyObject* args, PyObject* kwds) {
    static char const* kwnames[] = {"time", "group", nullptr};

    // this function shouldn't be called if we're not going for a soft
    // switch
    assert(slp_try_stackless);

    float seconds;
    uint8 group = UNSPECIFIED_GROUP;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "f|b:Sleep", (char**)kwnames, &seconds, &group))
        return nullptr;

    BEGIN_CPP
        CTasker* self = extract<CTasker*>(selfb);

        handle<> delta = self->SleepImpl(seconds, group, true);

        return incref(delta.get());
    END_CPP

    return nullptr;
}


PyObject* CTasker::StaticSleepPy(PyObject* self, PyObject* args, PyObject* kwds) {
    if (!msDefault) {
        PyErr_SetString(PyExc_RuntimeError, "default tasker not set");
        return nullptr;
    }
    PyObject* definst = msDefault->mSelf;
    Py_INCREF(definst);
    PyObject* retval = SleepPy(definst, args, kwds);
    Py_DECREF(definst);
    return retval;
}

float CTasker::Yield(uint8 group) {
    if (!GetRealGroup(group))
        throw ValueError("invalid group number");

#if SLEEP_YIELD_DISPATCH_ENABLED
    CIOManager::AddPendingDispatchCall();
#endif

    handle<> elapsed(PyChannel_Receive(mGroups[group].YieldChannel.get()));
    assert(PyFloat_CheckExact(elapsed.get()));

    return static_cast<float>(PyFloat_AS_DOUBLE(elapsed.get()));
}

float CTasker::StaticYield(uint8 group) {
    if (!msDefault)
        throw RuntimeError("default tasker not set");
    return msDefault->Yield(group);
}

PyObject* CTasker::YieldPy(PyObject* selfb, PyObject* args, PyObject* kwds) {
    static char const* kwnames[] = {"group", nullptr};

    // this function shouldn't be called if we're not going for a soft
    // switch
    assert(slp_try_stackless);

    uint8 group = UNSPECIFIED_GROUP;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|b:Yield", (char**)kwnames, &group))
        return nullptr;

    BEGIN_CPP
        CTasker* self = extract<CTasker*>(selfb);

        if (!self->GetRealGroup(group)) {
            PyErr_SetString(PyExc_ValueError, "invalid group");
            return nullptr;
        }

#if SLEEP_YIELD_DISPATCH_ENABLED
    CIOManager::AddPendingDispatchCall();
#endif

        return PyChannel_Receive(self->mGroups[group].YieldChannel.get());
    END_CPP

    return nullptr;
}

PyObject* CTasker::StaticYieldPy(PyObject* self, PyObject* args, PyObject* kwds) {
    if (!msDefault) {
        PyErr_SetString(PyExc_RuntimeError, "default tasker not set");
        return nullptr;
    }
    PyObject* definst = msDefault->mSelf;
    Py_INCREF(definst);
    PyObject* retval = YieldPy(definst, args, kwds);
    Py_DECREF(definst);
    return retval;
}

//PyObject* CTasker::StaticInsertTaskletPy(PyObject* self, PyObject* args, PyObject* kwds) {
//    if (!msDefault) {
//        PyErr_SetString(PyExc_RuntimeError, "default tasker not set");
//        return nullptr;
//    }
//    PyObject* definst = msDefault->mSelf;
//    Py_INCREF(definst);
//    //PyObject* retval = 
//    Py_DECREF(definst);
//    return nullptr;
//}

object CTasker::GetTaskletList(uint8 groupnum) const {
    if (groupnum >= mGroupCount && groupnum != NO_GROUPS && groupnum != ALL_GROUPS)
        throw ValueError("invalid group");

    list tasklets;

    if (groupnum == ALL_GROUPS) {
        for (int i = 0; i < mGroupCount; i++) {
            object t = GetTaskletList(i);
            if (t.is_none())
                continue;
            tasklets.extend(t);
        }
    }

    if (groupnum < mGroupCount) {
        FTaskletGroup const& group = mGroups[groupnum];
        // Add tasklets in the pending inserts lists
        typedef std::list<handle<PyTaskletObject>>::const_iterator InsertIter;
        for (InsertIter it = group.Inserts.begin(); it != group.Inserts.end(); it++) {
            if (PyTasklet_Alive(it->get()))
                tasklets.append(object(*it));
        }

        // Add tasklets yielded on channels
        PyChannelObject* chan = group.YieldChannel.get();
        if (chan->balance != 0) {
            PyTaskletObject* current = chan->head;
            while (current && current != (PyTaskletObject*)chan) {
                if (PyTasklet_Alive(current))
                    tasklets.append(handle<>(borrowed(current)));
                current = current->next;
            }
        }

        // Add sleeping tasklets
        typedef std::list<FSleepingTasklet>::const_iterator SleeperIter;
        for (SleeperIter it = group.Sleepers.begin(); it != group.Sleepers.end(); it++) {
            PyTaskletObject* tasklet = it->Channel.get()->head;
            if (tasklet && PyTasklet_Alive(tasklet)) {
                tasklets.append(handle<>(borrowed(tasklet)));
            }
        }
    }

    // If the group we're checking is currently running, add all scheduled
    // tasklets including the current one
    if (groupnum == mCurrentGroup || groupnum == NO_GROUPS) {
        object selfo = GetCurrentTasklet();
        if (!IsCurrentTaskletMain())
            tasklets.append(selfo);
        PyTaskletObject* self = (PyTaskletObject*)selfo.ptr();
        PyTaskletObject* current = self->next;
        while (current && current != self) {
            if (PyTasklet_Alive(current))
                tasklets.append(handle<>(borrowed(current)));
            current = current->next;
        }
    }

    if (len(tasklets) > 0)
        return tasklets;
    else
        return object();
}

void CTasker::KillTasklets(uint8 group, bool killCurrent) {
    if (group >= mGroupCount && group != ALL_GROUPS)
        throw ValueError("invalid group");

    object tasklets = GetTaskletList(group);
    bool killedCurrent = false;
    object current = GetCurrentTasklet();

    if (tasklets.is_none())
        return;

    PY_FOR(tasklet, tasklets) {
        if (!PyTasklet_Check(tasklet.ptr()))
            continue;
        if (!killCurrent && tasklet.ptr() == current.ptr())
            continue;
        int kr = PyTasklet_Kill((PyTaskletObject*)tasklet.ptr());
        if (kr == -1) {
            assert(PyErr_Occurred());
            throw_error_already_set();
        }
    }
}

int CTasker::ClearChannel(object channelO, object value) {
    PyChannelObject* channel = (PyChannelObject*)channelO.ptr();
    if (!PyChannel_Check((PyObject*)channel)) {
        PyErr_SetString(PyExc_TypeError, "channel must be instance of stackless.channel");
        throw_error_already_set();
    }
    int numCleared = 0;
    if (channel->balance) {
        for (int i = channel->balance * -1; i > 0; i--) {
            if (PyChannel_Send(channel, value.ptr()) == -1) {
                throw_error_already_set();
            }
            numCleared++;
        }
    }
    return numCleared;
}

object CTasker::GetTaskletExtClass() {
    return GTaskletExtClass;
}

KH_BEGIN_GC(CTasker)
    BOOST_FOREACH(FTaskletGroup& group, thisx->mGroups) {
        KH_GC_HANDLE_EX(group.YieldChannel, PyChannelObject)
        BOOST_FOREACH(FSleepingTasklet& sleeper, group.Sleepers) {
            KH_GC_HANDLE_EX(sleeper.Channel, PyChannelObject)
        }
        BOOST_FOREACH(handle<PyTaskletObject>& chan, group.Inserts) {
            KH_GC_HANDLE_EX(chan, PyTaskletObject)
        }
    }
KH_END_GC()

void _FiniTaskerBindings() {
    GTaskletExtClass = object();
    CTasker::msDefault.Reset();
}

namespace {

// Definitions for stackless-compatible sleep and yield functions.

PyMethodDef GDefSleep = {
    "Sleep", (PyCFunction)&CTasker::SleepPy,
    METH_VARARGS | METH_KEYWORDS | METH_STACKLESS,
    "Stackless implementation of the sleep function using soft switching."
};

PyMethodDef GDefYield = {
    "Yield", (PyCFunction)&CTasker::YieldPy,
    METH_VARARGS | METH_KEYWORDS | METH_STACKLESS,
    "Stackless implementation of the yield function using soft switching."
};

PyMethodDef GDefStaticSleep = {
    "StaticSleep", (PyCFunction)&CTasker::StaticSleepPy,
    METH_VARARGS | METH_KEYWORDS | METH_STACKLESS | METH_STATIC,
    "Sleep using default tasker"
};

PyMethodDef GDefStaticYield = {
    "StaticYield", (PyCFunction)&CTasker::StaticYieldPy,
    METH_VARARGS | METH_KEYWORDS | METH_STACKLESS | METH_STATIC,
    "Yield using default tasker"
};


} // namespace

void _InitTaskerBindings() {
    class_
        <FTaskletGroupOptions>
        ("FTaskletGroupOptions", init<>())
        .def_readwrite("Timeout", &FTaskletGroupOptions::Timeout)
        .def_readwrite("ThreadBlock", &FTaskletGroupOptions::ThreadBlock)
        .def_readwrite("SoftSwitch", &FTaskletGroupOptions::SoftSwitch)
        .def_readwrite("IgnoreNesting", &FTaskletGroupOptions::IgnoreNesting)
        .def_readwrite("TotalTimeout", &FTaskletGroupOptions::TotalTimeout)
        .def_readwrite("InsertStraysIntoDefault", &FTaskletGroupOptions::InsertStraysIntoDefault)
        .def_readwrite("RunPreference", &FTaskletGroupOptions::RunPreference)
        ;
    
    object TaskerType = class_
        <CTasker, bases<CSubsystem>, noncopyable>
        ("CTaskerBase",
            init<uint8, FTaskletGroupOptions>((
                arg("groupCount")=1, arg("defaultOptions")=FTaskletGroupOptions()
            ))
        )
        // methods
        .def("Tick", (void (CTasker::*)(float))&CTasker::Tick, arg("deltaTime"))
        .def("Tick", (void (CTasker::*)(float, uint8))&CTasker::Tick, (arg("deltaTime"), arg("group")))
        .def("Run", &CTasker::Run, arg("group"))
        .def("GetGroupOptions", &CTasker::GetGroupOptions, return_internal_reference<>())
        .def("CreateTasklet", &CTasker::CreateTasklet, (arg("callable")=object()))
        .def("InsertTasklet", &CTasker::InsertTasklet, (arg("tasklet"), arg("group")=0, arg("args")=object(), arg("kwds")=object()))
        .def("GetTaskletList", &CTasker::GetTaskletList)
        .def("KillTasklets", &CTasker::KillTasklets, (arg("group"), arg("killCurrent")=false))
        // static methods
        .def("StaticCreateTasklet", &CTasker::StaticCreateTasklet, (arg("callable")=object())).staticmethod("StaticCreateTasklet")
        .def("StaticInsertTasklet", &CTasker::StaticInsertTasklet, (arg("tasklet"), arg("args")=object(), arg("kwds")=object())).staticmethod("StaticInsertTasklet")
        .def("GetDefault", &CTasker::GetDefault).staticmethod("GetDefault")
        .def("SetDefault", &CTasker::SetDefault).staticmethod("SetDefault")
        .def("ClearChannel", &CTasker::ClearChannel).staticmethod("ClearChannel")
        // properties
        .add_property("GroupCount", &CTasker::GetGroupCount)
        .add_property("DefaultGroup", &CTasker::GetDefaultGroup, &CTasker::SetDefaultGroup)
        .add_property("CurrentGroup", &CTasker::GetCurrentGroup)
        // constants
        .setattr("MAX_GROUP_COUNT", CTasker::MAX_GROUP_COUNT)
        .setattr("LAST_GROUP", CTasker::LAST_GROUP)
        .setattr("ALL_GROUPS", CTasker::ALL_GROUPS)
        .setattr("NO_GROUPS", CTasker::NO_GROUPS)
        .setattr("CURRENT_GROUP", CTasker::CURRENT_GROUP)
        .setattr("DEFAULT_GROUP", CTasker::DEFAULT_GROUP)
        .setattr("UNSPECIFIED_GROUP", CTasker::UNSPECIFIED_GROUP)
        ;
    
    SetModuleAttr("CreateTasklet", TaskerType.attr("StaticCreateTasklet"));
    SetModuleAttr("InsertTasklet", TaskerType.attr("StaticInsertTasklet"));

    // Insert our raw python methods into the class
    {
        handle<> sleepd(PyDescr_NewMethod((PyTypeObject*)TaskerType.ptr(), &GDefSleep));
        handle<> yieldd(PyDescr_NewMethod((PyTypeObject*)TaskerType.ptr(), &GDefYield));
        TaskerType.attr("Sleep") = sleepd;
        TaskerType.attr("Yield") = yieldd;

        handle<> sleepcf(PyCFunction_NewEx(&GDefStaticSleep, nullptr, nullptr));
        handle<> yieldcf(PyCFunction_NewEx(&GDefStaticYield, nullptr, nullptr));
        handle<> sleepsm(PyStaticMethod_New(sleepcf.get()));
        handle<> yieldsm(PyStaticMethod_New(yieldcf.get()));
        TaskerType.attr("StaticSleep") = sleepsm;
        TaskerType.attr("StaticYield") = yieldsm;

        SetModuleAttr("Sleep", object(sleepcf));
        SetModuleAttr("Yield", object(yieldcf));
    }

    KH_IMPL_GC(CTasker);
    RegisterPtrConverter<CTasker>();

    GTaskletExtClass = GetInnerModuleAttr("CTaskletExt");
}

}}